Using Datasets Available Here: https://www.kaggle.com/dansbecker/nba-shot-logs (shot_log.csv)

And Here: https://www.basketball-reference.com/leagues/NBA_2015_totals.html (BBREF_players.csv)

This project attempts to identify patterns and create a framework for evaluating the performance value of basketball players by focusing on their scoring efficiency (ability to generate the most points in the least time).

Load in all required packages

suppressPackageStartupMessages(
  {
    package_list <- c('plotly', 'stringr', 'dplyr', 'sqldf', 'magrittr','RColorBrewer','webshot')
    non_installed <- package_list[!(package_list %in% installed.packages()[,"Package"])]
    if(length(non_installed)) install.packages(non_installed)
    library('plotly')
    library('RColorBrewer')
    library('dplyr')
    library('stringr')
    library('sqldf')
    library('magrittr')
    library('webshot')
  }
)

This dataset was posted on Kaggle.com, and contains a record of every shot by every player in every game of the 2014-15 season (as far as I can tell, there was too much data to check). This section simply loads the data and does some basic cleaning. The dataset included the point value of shots that were missed as well, so in cleaning the data I tested the implications of both penalizing missed shots (making them behave as the negative of their point value), and nuetralizing missed shots (making all missed point values equal to zero). For the purposes of this project it makes more sense to treat missed shots as zero point values, because the implications of misses to player performance can already be accounted for in the ratio of points per shot.

shots_DF <- dplyr::tbl_df(read.csv("shot_logs.csv"))
#Perform Cleaning
  #Penalize missed shots?
  #shots_DF$PTS_TYPE <- ifelse(shots_DF$SHOT_RESULT == "made", shots_DF$PTS_TYPE, -shots_DF$PTS_TYPE)
  #Nuetralize missed shots
shots_DF$PTS_TYPE <- ifelse(shots_DF$SHOT_RESULT == "made", shots_DF$PTS_TYPE, 0)
players <- sqldf("SELECT DISTINCT(player_id) AS \"ID\", player_name AS \"PLAYER\"
                          FROM shots_DF
                          ORDER BY ID")
players[1:10,]

The data on in-game performance and shooting tendencies contains a lot of very specific information on player performance that could be used to get a high-resolution look at exactly how individuals performed in different situations. Instead of looking for a fine grained means of quantifying player performance in different situations, this analysis will pull out aggregate results to create a generalized means of ranking players on their ability to generate points, given the limited amount of time they have to handle the ball. Since Basketball games are short and high scoring, there is reason to believe that the best (offensive) players should have the highest ratio of points per shot to minutes of ball time. This study will look at the implications of using only this low resolution artificial ranking system, and maybe compare it to ranking systems that account for more features. As can be seen from the ggplot2 graphic, more ball time and low point per shot ratio result in a lower ranking value. Since taking more shots requires more ball-time, the players that tend to have the most ball time and publicity arent ranked very highly with respect to this metric (Kobe Bryant, and Lebron James are pretty low). The plotly graphic is far more useful and interactive in showing the same information, but unfortunately it doesn’t live in the notebook.

players_stats_data <- sqldf("SELECT players.ID, players.PLAYER AS \"Player\", 
                   SUM(DRIBBLES) AS \"Dribbles\", COUNT(SHOT_NUMBER) AS \"Total_Shots\",
                   SUM(PTS_TYPE) AS \"Point_Total\",
                   SUM(PTS_TYPE)/COUNT(SHOT_NUMBER) AS \"Points_Per_Shot\",
                   SUM(TOUCH_TIME) AS \"Ball_Time\"
                   FROM players INNER JOIN shots_DF
                   ON players.ID = shots_DF.player_id
                   GROUP BY players.ID
                   ORDER BY Points_Per_Shot DESC")
#Ranking Function For Identifying Top Player Performance
player_stats_rankfun <- function(x)
{
  vals <- c()
  for(i in 1:dim(x)[1])
  {
    pos <- x[i,]
    val <- pos$Ball_Time / pos$Point_Total
    vals <- c(vals, val)
  }
  return(data.frame(Ranking = vals, Rank = rank(vals)))
}
rankings <- player_stats_rankfun(players_stats_data)
players_stats_data_ranks <- cbind(player_stats_rankfun(players_stats_data), players_stats_data)
players_stats_plot_plotly <- plot_ly(players_stats_data_ranks) %>%
                 add_markers(x = ~Total_Shots, y = ~Points_Per_Shot, color = ~Ranking,
                             size = ~Ball_Time, colors = "Spectral",
                             text = ~paste("Player: ", Player,
                                           "</br>Rank: ", Rank, ", Value: ", round(Ranking, digits = 3),
                                           "</br>Points per Shot: ", round(Points_Per_Shot, digits = 3),
                                           "</br>Ball Time: ", round(Ball_Time, digits = 3),
                                           "</br>Dribbles: ", Dribbles,
                                           "</br>Total Points: ", Point_Total,
                                           "</br>Total Shots: ", Total_Shots)) %>%
                 layout(title = paste0("Player Rankings"),
                        xaxis = list(title = "Total Shots"),
                        yaxis = list(title = "Points Per Shot"))
players_stats_plot_gg <- ggplot() + 
                      geom_point(data = players_stats_data_ranks, aes(x = Total_Shots, y = Points_Per_Shot, colour = Ranking, size = Ball_Time))
                      
#ggplot2 Graphic
#players_stats_plot_gg
#Plotly Graphic
players_stats_plot_plotly

In order to get a few more metrics on player performance, the first dataset was joined with data from basketball-reference.com. This dataset contained player specific information on seasonal performance, including position, age, games played etc., many of which were completely unattainable from the dataset on in-game shooting. Using both datasets allows for a fuller picture of the individual player’s performance, and offers a means to check the implications of the rankings and the consistency of the data.

BREF_data <- dplyr::tbl_df(read.csv("BBREF_players.csv"))
players_rank <- BREF_data
#Perform Cleaning
players_rank$Player <- str_extract(players_rank$Player, "\\w+ \\w+\\b")
players_rank <- players_rank[!is.na(players_rank$Player),]
players_rank <- distinct(players_rank, Player, .keep_all = TRUE)
player_stats_ranking_data <- sqldf("Select players_stats_data_ranks.Rank, Ranking, players_rank.Player, Pos As Position, Age, Tm AS Team, G As Games, MP AS Minutes_Total, 
                                    Ball_Time, Dribbles, Total_Shots, Point_Total, Points_Per_Shot, 
                                    FG AS Field_Goals, FGA AS Field_Goal_Att
                                    FROM players_rank INNER JOIN players_stats_data_ranks
                                    ON LOWER(players_rank.Player) = players_stats_data_ranks.Player
                                    ORDER BY Position, Rank")
#Perform Cleaning
player_stats_ranking_data <- player_stats_ranking_data[player_stats_ranking_data$Position %in% c("C","PF","PG","SF", "SG"),]
player_stats_ranking_data <- as.data.frame(lapply(player_stats_ranking_data, 
                                                  FUN = function(X)
                                                        {
                                                          if(!is.numeric(X))
                                                          {
                                                            return(as.character(X))
                                                          }
                                                          else
                                                          {
                                                            return(round(X, digits = 3))
                                                          }
                                                        }
                                                  ), stringsAsFactors = FALSE
                                            )
player_stats_ranking_data[1:10,]

This tangent looks at how player performance stacks up when grouping by position. This will show if the ranking system is dramatically biased towards the skills required for any given position. Looking at the ggplot graphic generated by averaging all the metrics after aggregating by position, it is clear that centers are ranked much higher than other positions when looking at how often they score given the amount of time they handle the ball. The surprise is that the centers had a much higher points-per-shot ratio but also less ball time, which contributed to the higher average score and lower ranking.

avg_position_ranks <- sqldf("SELECT Position, COUNT(Position) AS Number, AVG(Rank) AS Rank, AVG(Age) As Age,
                             AVG(Points_Per_Shot) AS Points_Per_Shot, AVG(Ball_Time) AS Ball_Time,
                             AVG(Point_Total) AS Point_Total, AVG(Total_Shots) As Total_Shots
                             FROM player_stats_ranking_data
                             GROUP BY Position
                             ORDER BY Rank, Number")
#Perform Cleaning
avg_position_ranks <- as.data.frame(lapply(avg_position_ranks, 
                                                  FUN = function(X, n = 2)
                                                        {
                                                          if(!is.numeric(X))
                                                          {
                                                            return(as.character(X))
                                                          }
                                                          else
                                                          {
                                                            return(round(X, digits = n))
                                                          }
                                                        }
                                                  ), stringsAsFactors = FALSE
                                            )
avg_position_ranks_plotly <- plot_ly(avg_position_ranks) %>%
                           add_bars(x = ~Position, y = ~Rank, color = ~Points_Per_Shot,
                                      size = ~Number, colors = "RdBu",
                                      text = ~paste("</br>Number: ", Number,
                                                    "</br>Points Per Shot: ", Points_Per_Shot,
                                                    "</br>Point Total: ", Point_Total,
                                                    "</br>Shot Total: ", Total_Shots,
                                                    "</br>Ball Time: ", Ball_Time,
                                                    "</br>Age: ", Age) ) %>%
                          layout(title = "Positional Rankings",
                                 xaxis = list(title = "Position"),
                                 yaxis = list(title = "Average Ranking"),
                                 bargap = ~Number)
avg_position_ranks_gg <- ggplot() +
                         geom_col(data = avg_position_ranks, aes(x = Position, y = Rank, fill = Points_Per_Shot))
#ggplot2 Graphics
#avg_position_ranks_gg
#Plotly Graphic
avg_position_ranks_plotly

This ggplot graphic shows how this ranking system plays out with respect to a player’s point total, accounting for position and ball time as well. As one would expect players with high point totals have the highest rankings, and this generally corresponds to more ball time. Looking at the graphic it is also clear that different positions have defined clustering within the data, something that could be explored further.

                          
player_position_ranks_plotly <-  plot_ly() %>%
                          add_data(player_stats_ranking_data) %>%
                          add_markers(x = ~Ranking, y = ~Point_Total, z = ~Points_Per_Shot,
                                      size = ~Ball_Time, color = ~Position, colors = "Spectral",
                                      text = ~paste("</br>Player: ", Player,
                                                    "</br>Rank: ", Rank, ", Value: ", Ranking,
                                                    "</br>Position: ", Position,
                                                    "</br>Points Per Shot: ", Points_Per_Shot,
                                                    "</br>Point Total: ", Point_Total,
                                                    "</br>Shot Total: ", Total_Shots,
                                                    "</br>Ball Time: ", Ball_Time,
                                                    "</br>Age: ", Age) ) %>%
                          layout(title = "Player Positional Rankings")
                          
player_position_ranks_gg <- ggplot() + 
                            geom_point(data = player_stats_ranking_data, aes(x = Ranking, y = Point_Total, size = Ball_Time, color = Position))
#ggplot2 Graphic
#player_position_ranks_gg
#Plotly Graphic
player_position_ranks_plotly

The positional rankings exist in clusters, which means that the rankings for players of the same position are grouped around an expected value (average) with some standard deviation within the grouping. By modeling the probabilities of players in each position having a certain ranking as a normal distribution, a set of boundaries for identifying a player’s position by their ranking within this system begins to appear. For example, the most confident estimate that a player with a certain ranking plays a certain position will map directly to the positional distribution with the highest value for that ranking (e.g. if a player’s ranking is less than 1.00, then ‘center’ would be the best estimate for their position.)

positions <- unlist(sqldf("SELECT DISTINCT( position ) FROM player_stats_ranking_data"))
position_ranks_stats <- as.data.frame(matrix(0, ncol = length(positions), nrow  = 2))
colnames(position_ranks_stats) <- positions
rownames(position_ranks_stats) <- c("mean", "stdDist")
colors <- c("red", "green", "blue", "yellow", "purple")
n <- 1
position_ranks_dist_plotly <- plot_ly()
for(pos in positions)
{
  data <- player_stats_ranking_data[player_stats_ranking_data$Position == pos, "Ranking"]
  position_ranks_stats[,pos] = c(mean(data), sd(data))  
  yval <- dnorm(data, position_ranks_stats[1,pos], position_ranks_stats[2,pos])
  
  position_ranks_dist_plotly <- position_ranks_dist_plotly %>% 
                                add_markers(x = data, y = yval, colors = "Spectral", name = pos) %>%
                                add_lines(x = data, y = yval, 
                                          name = pos, text = paste("Mean Ranking: ", position_ranks_stats[1,pos],
                                                                   "</br> Std Deviation: ", position_ranks_stats[2, pos]), colors = "Spectral")
                                
}
position_ranks_dist_plotly <- position_ranks_dist_plotly %>%
                              layout(title = "Position Ranking Distributions", xaxis = list(title = "Ranking Value"),
                                     yaxis = list(title = "Probability %"))
datafull <- data.frame("Ranking" = player_stats_ranking_data[player_stats_ranking_data$Position == "C", "Ranking"][1:47],
                    "PF" = player_stats_ranking_data[player_stats_ranking_data$Position == "PF", "Ranking"][1:47],
                    "PG" = player_stats_ranking_data[player_stats_ranking_data$Position == "PG", "Ranking"][1:47],
                    "SF" = player_stats_ranking_data[player_stats_ranking_data$Position == "SF", "Ranking"][1:47],
                    "SG" = player_stats_ranking_data[player_stats_ranking_data$Position == "SG", "Ranking"][1:47])
datafull$Percentage <- dnorm(datafull$Ranking, position_ranks_stats[1,1], position_ranks_stats[2,1])
datafull$valPF <- dnorm(datafull$PF, position_ranks_stats[1,2], position_ranks_stats[2,2])
datafull$valPG<- dnorm(datafull$PG, position_ranks_stats[1,3], position_ranks_stats[2,3])
datafull$valSF <- dnorm(datafull$SF, position_ranks_stats[1,4], position_ranks_stats[2,4])
datafull$valSG <- dnorm(datafull$SG, position_ranks_stats[1,5], position_ranks_stats[2,5])
position_ranks_dist_gg <- ggplot(datafull) +
                          geom_line(aes(x = Ranking, y = Percentage, color = "C", name = "C")) +
                          geom_path(aes(x = PF, y = valPF, color = "PF", name = "PF")) +
                          geom_path(aes(x = PG, y = valPG, color = "PG", name = "PG")) +
                          geom_path(aes(x = SF, y = valSF, color = "SF", name = "SF")) +
                          geom_path(aes(x = SG, y = valSG, color = "SG", name = "SG"))
#ggplot Graphic
#position_ranks_dist_gg
#plotly Graphic
position_ranks_dist_plotly
LS0tDQp0aXRsZTogIk5CQSBQbGF5ZXIgUGVyZm9ybWFuY2UgMjAxNC0yMDE1Ig0KYXV0aG9yOiAicGxvdGx5Ig0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOiBkZWZhdWx0DQogIGh0bWxfZG9jdW1lbnQ6IGRlZmF1bHQNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQotLS0NCg0KI1VzaW5nIERhdGFzZXRzIEF2YWlsYWJsZSBIZXJlOiBodHRwczovL3d3dy5rYWdnbGUuY29tL2RhbnNiZWNrZXIvbmJhLXNob3QtbG9ncyAoc2hvdF9sb2cuY3N2KQ0KI0FuZCBIZXJlOiBodHRwczovL3d3dy5iYXNrZXRiYWxsLXJlZmVyZW5jZS5jb20vbGVhZ3Vlcy9OQkFfMjAxNV90b3RhbHMuaHRtbCAoQkJSRUZfcGxheWVycy5jc3YpDQoNClRoaXMgcHJvamVjdCBhdHRlbXB0cyB0byBpZGVudGlmeSBwYXR0ZXJucyBhbmQgY3JlYXRlIGEgZnJhbWV3b3JrIGZvciBldmFsdWF0aW5nIHRoZSBwZXJmb3JtYW5jZSB2YWx1ZSBvZiBiYXNrZXRiYWxsIHBsYXllcnMgYnkgZm9jdXNpbmcgb24gdGhlaXIgc2NvcmluZyBlZmZpY2llbmN5IChhYmlsaXR5IHRvIGdlbmVyYXRlIHRoZSBtb3N0IHBvaW50cyBpbiB0aGUgbGVhc3QgdGltZSkuDQoNCkxvYWQgaW4gYWxsIHJlcXVpcmVkIHBhY2thZ2VzDQpgYGB7cn0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChtZXNzYWdlID0gRiwgd2FybmluZyA9IEYsIHN0cmlwLndoaXRlID0gRiwgdGlkeSA9IFQpDQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoDQogIHsNCiAgICBwYWNrYWdlX2xpc3QgPC0gYygncGxvdGx5JywgJ3RpZHl2ZXJzZScsICdzcWxkZicsJ1JDb2xvckJyZXdlcicsJ3dlYnNob3QnKQ0KICAgIG5vbl9pbnN0YWxsZWQgPC0gcGFja2FnZV9saXN0WyEocGFja2FnZV9saXN0ICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKClbLCJQYWNrYWdlIl0pXQ0KICAgIGlmKGxlbmd0aChub25faW5zdGFsbGVkKSkgaW5zdGFsbC5wYWNrYWdlcyhub25faW5zdGFsbGVkKQ0KICAgIGxpYnJhcnkoJ3Bsb3RseScpDQogICAgbGlicmFyeSgnUkNvbG9yQnJld2VyJykNCiAgICBsaWJyYXJ5KCd0aWR5dmVyc2UnKQ0KICAgIGxpYnJhcnkoJ3NxbGRmJykNCiAgICBsaWJyYXJ5KCd3ZWJzaG90JykNCiAgfQ0KKQ0KYGBgDQoNCg0KVGhpcyBkYXRhc2V0IHdhcyBwb3N0ZWQgb24gS2FnZ2xlLmNvbSwgYW5kIGNvbnRhaW5zIGEgcmVjb3JkIG9mIGV2ZXJ5IHNob3QgYnkgZXZlcnkgcGxheWVyIGluIGV2ZXJ5IGdhbWUgb2YgdGhlIDIwMTQtMTUgc2Vhc29uIChhcyBmYXIgYXMgSSBjYW4gdGVsbCwgdGhlcmUgd2FzIHRvbyBtdWNoIGRhdGEgdG8gY2hlY2spLiAgVGhpcyBzZWN0aW9uIHNpbXBseSBsb2FkcyB0aGUgZGF0YSBhbmQgZG9lcyBzb21lIGJhc2ljIGNsZWFuaW5nLiAgVGhlIGRhdGFzZXQgaW5jbHVkZWQgdGhlIHBvaW50IHZhbHVlIG9mIHNob3RzIHRoYXQgd2VyZSBtaXNzZWQgYXMgd2VsbCwgc28gaW4gY2xlYW5pbmcgdGhlIGRhdGEgSSB0ZXN0ZWQgdGhlIGltcGxpY2F0aW9ucyBvZiBib3RoIHBlbmFsaXppbmcgbWlzc2VkIHNob3RzIChtYWtpbmcgdGhlbSBiZWhhdmUgYXMgdGhlIG5lZ2F0aXZlIG9mIHRoZWlyIHBvaW50IHZhbHVlKSwgYW5kIG51ZXRyYWxpemluZyBtaXNzZWQgc2hvdHMgKG1ha2luZyBhbGwgbWlzc2VkIHBvaW50IHZhbHVlcyBlcXVhbCB0byB6ZXJvKS4gIEZvciB0aGUgcHVycG9zZXMgb2YgdGhpcyBwcm9qZWN0IGl0IG1ha2VzIG1vcmUgc2Vuc2UgdG8gdHJlYXQgbWlzc2VkIHNob3RzIGFzIHplcm8gcG9pbnQgdmFsdWVzLCBiZWNhdXNlIHRoZSBpbXBsaWNhdGlvbnMgb2YgbWlzc2VzIHRvIHBsYXllciBwZXJmb3JtYW5jZSBjYW4gYWxyZWFkeSBiZSBhY2NvdW50ZWQgZm9yIGluIHRoZSByYXRpbyBvZiBwb2ludHMgcGVyIHNob3QuDQpgYGB7cn0NCg0Kc2hvdHNfREYgPC0gZHBseXI6OnRibF9kZihyZWFkLmNzdigic2hvdF9sb2dzLmNzdiIpKQ0KDQojUGVyZm9ybSBDbGVhbmluZw0KICAjUGVuYWxpemUgbWlzc2VkIHNob3RzPw0KICAjc2hvdHNfREYkUFRTX1RZUEUgPC0gaWZlbHNlKHNob3RzX0RGJFNIT1RfUkVTVUxUID09ICJtYWRlIiwgc2hvdHNfREYkUFRTX1RZUEUsIC1zaG90c19ERiRQVFNfVFlQRSkNCg0KDQogICNOdWV0cmFsaXplIG1pc3NlZCBzaG90cw0Kc2hvdHNfREYkUFRTX1RZUEUgPC0gaWZlbHNlKHNob3RzX0RGJFNIT1RfUkVTVUxUID09ICJtYWRlIiwgc2hvdHNfREYkUFRTX1RZUEUsIDApDQoNCnBsYXllcnMgPC0gc3FsZGYoIlNFTEVDVCBESVNUSU5DVChwbGF5ZXJfaWQpIEFTIFwiSURcIiwgcGxheWVyX25hbWUgQVMgXCJQTEFZRVJcIg0KICAgICAgICAgICAgICAgICAgICAgICAgICBGUk9NIHNob3RzX0RGDQogICAgICAgICAgICAgICAgICAgICAgICAgIE9SREVSIEJZIElEIikNCnBsYXllcnNbMToxMCxdDQoNCmBgYA0KDQoNCg0KVGhlIGRhdGEgb24gaW4tZ2FtZSBwZXJmb3JtYW5jZSBhbmQgc2hvb3RpbmcgdGVuZGVuY2llcyBjb250YWlucyBhIGxvdCBvZiB2ZXJ5IHNwZWNpZmljIGluZm9ybWF0aW9uIG9uIHBsYXllciBwZXJmb3JtYW5jZSB0aGF0IGNvdWxkIGJlIHVzZWQgdG8gZ2V0IGEgaGlnaC1yZXNvbHV0aW9uIGxvb2sgYXQgZXhhY3RseSBob3cgaW5kaXZpZHVhbHMgcGVyZm9ybWVkIGluIGRpZmZlcmVudCBzaXR1YXRpb25zLiAgSW5zdGVhZCBvZiBsb29raW5nIGZvciBhIGZpbmUgZ3JhaW5lZCBtZWFucyBvZiBxdWFudGlmeWluZyBwbGF5ZXIgcGVyZm9ybWFuY2UgaW4gZGlmZmVyZW50IHNpdHVhdGlvbnMsIHRoaXMgYW5hbHlzaXMgd2lsbCBwdWxsIG91dCBhZ2dyZWdhdGUgcmVzdWx0cyB0byBjcmVhdGUgYSBnZW5lcmFsaXplZCBtZWFucyBvZiByYW5raW5nIHBsYXllcnMgb24gdGhlaXIgYWJpbGl0eSB0byBnZW5lcmF0ZSBwb2ludHMsIGdpdmVuIHRoZSBsaW1pdGVkIGFtb3VudCBvZiB0aW1lIHRoZXkgaGF2ZSB0byBoYW5kbGUgdGhlIGJhbGwuICBTaW5jZSBCYXNrZXRiYWxsIGdhbWVzIGFyZSBzaG9ydCBhbmQgaGlnaCBzY29yaW5nLCB0aGVyZSBpcyByZWFzb24gdG8gYmVsaWV2ZSB0aGF0IHRoZSBiZXN0IChvZmZlbnNpdmUpIHBsYXllcnMgc2hvdWxkIGhhdmUgdGhlIGhpZ2hlc3QgcmF0aW8gb2YgcG9pbnRzIHBlciBzaG90IHRvIG1pbnV0ZXMgb2YgYmFsbCB0aW1lLiBUaGlzIHN0dWR5IHdpbGwgbG9vayBhdCB0aGUgaW1wbGljYXRpb25zIG9mIHVzaW5nIG9ubHkgdGhpcyBsb3cgcmVzb2x1dGlvbiBhcnRpZmljaWFsIHJhbmtpbmcgc3lzdGVtLCBhbmQgbWF5YmUgY29tcGFyZSBpdCB0byByYW5raW5nIHN5c3RlbXMgdGhhdCBhY2NvdW50IGZvciBtb3JlIGZlYXR1cmVzLg0KQXMgY2FuIGJlIHNlZW4gZnJvbSB0aGUgZ2dwbG90MiBncmFwaGljLCBtb3JlIGJhbGwgdGltZSBhbmQgbG93IHBvaW50IHBlciBzaG90IHJhdGlvIHJlc3VsdCBpbiBhIGxvd2VyIHJhbmtpbmcgdmFsdWUuICBTaW5jZSB0YWtpbmcgbW9yZSBzaG90cyByZXF1aXJlcyBtb3JlIGJhbGwtdGltZSwgdGhlIHBsYXllcnMgdGhhdCB0ZW5kIHRvIGhhdmUgdGhlIG1vc3QgYmFsbCB0aW1lIGFuZCBwdWJsaWNpdHkgYXJlbnQgcmFua2VkIHZlcnkgaGlnaGx5IHdpdGggcmVzcGVjdCB0byB0aGlzIG1ldHJpYyAoS29iZSBCcnlhbnQsIGFuZCBMZWJyb24gSmFtZXMgYXJlIHByZXR0eSBsb3cpLiAgVGhlIHBsb3RseSBncmFwaGljIGlzIGZhciBtb3JlIHVzZWZ1bCBhbmQgaW50ZXJhY3RpdmUgaW4gc2hvd2luZyB0aGUgc2FtZSBpbmZvcm1hdGlvbiwgYnV0IHVuZm9ydHVuYXRlbHkgaXQgZG9lc24ndCBsaXZlIGluIHRoZSBub3RlYm9vay4NCmBgYHtyfQ0KcGxheWVyc19zdGF0c19kYXRhIDwtIHNxbGRmKCJTRUxFQ1QgcGxheWVycy5JRCwgcGxheWVycy5QTEFZRVIgQVMgXCJQbGF5ZXJcIiwgDQogICAgICAgICAgICAgICAgICAgU1VNKERSSUJCTEVTKSBBUyBcIkRyaWJibGVzXCIsIENPVU5UKFNIT1RfTlVNQkVSKSBBUyBcIlRvdGFsX1Nob3RzXCIsDQogICAgICAgICAgICAgICAgICAgU1VNKFBUU19UWVBFKSBBUyBcIlBvaW50X1RvdGFsXCIsDQogICAgICAgICAgICAgICAgICAgU1VNKFBUU19UWVBFKS9DT1VOVChTSE9UX05VTUJFUikgQVMgXCJQb2ludHNfUGVyX1Nob3RcIiwNCiAgICAgICAgICAgICAgICAgICBTVU0oVE9VQ0hfVElNRSkgQVMgXCJCYWxsX1RpbWVcIg0KICAgICAgICAgICAgICAgICAgIEZST00gcGxheWVycyBJTk5FUiBKT0lOIHNob3RzX0RGDQogICAgICAgICAgICAgICAgICAgT04gcGxheWVycy5JRCA9IHNob3RzX0RGLnBsYXllcl9pZA0KICAgICAgICAgICAgICAgICAgIEdST1VQIEJZIHBsYXllcnMuSUQNCiAgICAgICAgICAgICAgICAgICBPUkRFUiBCWSBQb2ludHNfUGVyX1Nob3QgREVTQyIpDQoNCg0KI1JhbmtpbmcgRnVuY3Rpb24gRm9yIElkZW50aWZ5aW5nIFRvcCBQbGF5ZXIgUGVyZm9ybWFuY2UNCnBsYXllcl9zdGF0c19yYW5rZnVuIDwtIGZ1bmN0aW9uKHgpDQp7DQogIHZhbHMgPC0gYygpDQogIGZvcihpIGluIDE6ZGltKHgpWzFdKQ0KICB7DQogICAgcG9zIDwtIHhbaSxdDQogICAgdmFsIDwtIHBvcyRCYWxsX1RpbWUgLyBwb3MkUG9pbnRfVG90YWwNCiAgICB2YWxzIDwtIGModmFscywgdmFsKQ0KICB9DQogIHJldHVybihkYXRhLmZyYW1lKFJhbmtpbmcgPSB2YWxzLCBSYW5rID0gcmFuayh2YWxzKSkpDQp9DQoNCnJhbmtpbmdzIDwtIHBsYXllcl9zdGF0c19yYW5rZnVuKHBsYXllcnNfc3RhdHNfZGF0YSkNCg0KcGxheWVyc19zdGF0c19kYXRhX3JhbmtzIDwtIGNiaW5kKHBsYXllcl9zdGF0c19yYW5rZnVuKHBsYXllcnNfc3RhdHNfZGF0YSksIHBsYXllcnNfc3RhdHNfZGF0YSkNCg0KDQpwbGF5ZXJzX3N0YXRzX3Bsb3RfcGxvdGx5IDwtIHBsb3RfbHkocGxheWVyc19zdGF0c19kYXRhX3JhbmtzKSAlPiUNCiAgICAgICAgICAgICAgICAgYWRkX21hcmtlcnMoeCA9IH5Ub3RhbF9TaG90cywgeSA9IH5Qb2ludHNfUGVyX1Nob3QsIGNvbG9yID0gflJhbmtpbmcsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSB+QmFsbF9UaW1lLCBjb2xvcnMgPSAiU3BlY3RyYWwiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXh0ID0gfnBhc3RlKCJQbGF5ZXI6ICIsIFBsYXllciwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiPC9icj5SYW5rOiAiLCBSYW5rLCAiLCBWYWx1ZTogIiwgcm91bmQoUmFua2luZywgZGlnaXRzID0gMyksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjwvYnI+UG9pbnRzIHBlciBTaG90OiAiLCByb3VuZChQb2ludHNfUGVyX1Nob3QsIGRpZ2l0cyA9IDMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI8L2JyPkJhbGwgVGltZTogIiwgcm91bmQoQmFsbF9UaW1lLCBkaWdpdHMgPSAzKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiPC9icj5EcmliYmxlczogIiwgRHJpYmJsZXMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjwvYnI+VG90YWwgUG9pbnRzOiAiLCBQb2ludF9Ub3RhbCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiPC9icj5Ub3RhbCBTaG90czogIiwgVG90YWxfU2hvdHMpKSAlPiUNCiAgICAgICAgICAgICAgICAgbGF5b3V0KHRpdGxlID0gcGFzdGUwKCJQbGF5ZXIgUmFua2luZ3MiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJUb3RhbCBTaG90cyIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIlBvaW50cyBQZXIgU2hvdCIpKQ0KDQoNCnBsYXllcnNfc3RhdHNfcGxvdF9nZyA8LSBnZ3Bsb3QoKSArIA0KICAgICAgICAgICAgICAgICAgICAgIGdlb21fcG9pbnQoZGF0YSA9IHBsYXllcnNfc3RhdHNfZGF0YV9yYW5rcywgYWVzKHggPSBUb3RhbF9TaG90cywgeSA9IFBvaW50c19QZXJfU2hvdCwgY29sb3VyID0gUmFua2luZywgc2l6ZSA9IEJhbGxfVGltZSkpDQogICAgICAgICAgICAgICAgICAgICAgDQoNCiNnZ3Bsb3QyIEdyYXBoaWMNCiNwbGF5ZXJzX3N0YXRzX3Bsb3RfZ2cNCg0KI1Bsb3RseSBHcmFwaGljDQpwbGF5ZXJzX3N0YXRzX3Bsb3RfcGxvdGx5DQpgYGANCg0KDQoNCkluIG9yZGVyIHRvIGdldCBhIGZldyBtb3JlIG1ldHJpY3Mgb24gcGxheWVyIHBlcmZvcm1hbmNlLCB0aGUgZmlyc3QgZGF0YXNldCB3YXMgam9pbmVkIHdpdGggZGF0YSBmcm9tIGJhc2tldGJhbGwtcmVmZXJlbmNlLmNvbS4gIFRoaXMgZGF0YXNldCBjb250YWluZWQgcGxheWVyIHNwZWNpZmljIGluZm9ybWF0aW9uIG9uIHNlYXNvbmFsIHBlcmZvcm1hbmNlLCBpbmNsdWRpbmcgcG9zaXRpb24sIGFnZSwgZ2FtZXMgcGxheWVkIGV0Yy4sIG1hbnkgb2Ygd2hpY2ggd2VyZSBjb21wbGV0ZWx5IHVuYXR0YWluYWJsZSBmcm9tIHRoZSBkYXRhc2V0IG9uIGluLWdhbWUgc2hvb3RpbmcuIFVzaW5nIGJvdGggZGF0YXNldHMgYWxsb3dzIGZvciBhIGZ1bGxlciBwaWN0dXJlIG9mIHRoZSBpbmRpdmlkdWFsIHBsYXllcidzIHBlcmZvcm1hbmNlLCBhbmQgb2ZmZXJzIGEgbWVhbnMgdG8gY2hlY2sgdGhlIGltcGxpY2F0aW9ucyBvZiB0aGUgcmFua2luZ3MgYW5kIHRoZSBjb25zaXN0ZW5jeSBvZiB0aGUgZGF0YS4NCmBgYHtyfQ0KQlJFRl9kYXRhIDwtIGRwbHlyOjp0YmxfZGYocmVhZC5jc3YoIkJCUkVGX3BsYXllcnMuY3N2IikpDQpwbGF5ZXJzX3JhbmsgPC0gQlJFRl9kYXRhDQoNCiNQZXJmb3JtIENsZWFuaW5nDQpwbGF5ZXJzX3JhbmskUGxheWVyIDwtIHN0cl9leHRyYWN0KHBsYXllcnNfcmFuayRQbGF5ZXIsICJcXHcrIFxcdytcXGIiKQ0KcGxheWVyc19yYW5rIDwtIHBsYXllcnNfcmFua1shaXMubmEocGxheWVyc19yYW5rJFBsYXllciksXQ0KcGxheWVyc19yYW5rIDwtIGRpc3RpbmN0KHBsYXllcnNfcmFuaywgUGxheWVyLCAua2VlcF9hbGwgPSBUUlVFKQ0KDQpwbGF5ZXJfc3RhdHNfcmFua2luZ19kYXRhIDwtIHNxbGRmKCJTZWxlY3QgcGxheWVyc19zdGF0c19kYXRhX3JhbmtzLlJhbmssIFJhbmtpbmcsIHBsYXllcnNfcmFuay5QbGF5ZXIsIFBvcyBBcyBQb3NpdGlvbiwgQWdlLCBUbSBBUyBUZWFtLCBHIEFzIEdhbWVzLCBNUCBBUyBNaW51dGVzX1RvdGFsLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEJhbGxfVGltZSwgRHJpYmJsZXMsIFRvdGFsX1Nob3RzLCBQb2ludF9Ub3RhbCwgUG9pbnRzX1Blcl9TaG90LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEZHIEFTIEZpZWxkX0dvYWxzLCBGR0EgQVMgRmllbGRfR29hbF9BdHQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEZST00gcGxheWVyc19yYW5rIElOTkVSIEpPSU4gcGxheWVyc19zdGF0c19kYXRhX3JhbmtzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPTiBMT1dFUihwbGF5ZXJzX3JhbmsuUGxheWVyKSA9IHBsYXllcnNfc3RhdHNfZGF0YV9yYW5rcy5QbGF5ZXINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE9SREVSIEJZIFBvc2l0aW9uLCBSYW5rIikNCg0KDQojUGVyZm9ybSBDbGVhbmluZw0KcGxheWVyX3N0YXRzX3JhbmtpbmdfZGF0YSA8LSBwbGF5ZXJfc3RhdHNfcmFua2luZ19kYXRhW3BsYXllcl9zdGF0c19yYW5raW5nX2RhdGEkUG9zaXRpb24gJWluJSBjKCJDIiwiUEYiLCJQRyIsIlNGIiwgIlNHIiksXQ0KcGxheWVyX3N0YXRzX3JhbmtpbmdfZGF0YSA8LSBhcy5kYXRhLmZyYW1lKGxhcHBseShwbGF5ZXJfc3RhdHNfcmFua2luZ19kYXRhLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRlVOID0gZnVuY3Rpb24oWCkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmKCFpcy5udW1lcmljKFgpKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybihhcy5jaGFyYWN0ZXIoWCkpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsc2UNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4ocm91bmQoWCwgZGlnaXRzID0gMykpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApDQpwbGF5ZXJfc3RhdHNfcmFua2luZ19kYXRhWzE6MTAsXQ0KDQpgYGANCg0KDQoNClRoaXMgdGFuZ2VudCBsb29rcyBhdCBob3cgcGxheWVyIHBlcmZvcm1hbmNlIHN0YWNrcyB1cCB3aGVuIGdyb3VwaW5nIGJ5IHBvc2l0aW9uLiAgVGhpcyB3aWxsIHNob3cgaWYgdGhlIHJhbmtpbmcgc3lzdGVtIGlzIGRyYW1hdGljYWxseSBiaWFzZWQgdG93YXJkcyB0aGUgc2tpbGxzIHJlcXVpcmVkIGZvciBhbnkgZ2l2ZW4gcG9zaXRpb24uICBMb29raW5nIGF0IHRoZSBnZ3Bsb3QgZ3JhcGhpYyBnZW5lcmF0ZWQgYnkgYXZlcmFnaW5nIGFsbCB0aGUgbWV0cmljcyBhZnRlciBhZ2dyZWdhdGluZyBieSBwb3NpdGlvbiwgaXQgaXMgY2xlYXIgdGhhdCBjZW50ZXJzIGFyZSByYW5rZWQgbXVjaCBoaWdoZXIgdGhhbiBvdGhlciBwb3NpdGlvbnMgd2hlbiBsb29raW5nIGF0IGhvdyBvZnRlbiB0aGV5IHNjb3JlIGdpdmVuIHRoZSBhbW91bnQgb2YgdGltZSB0aGV5IGhhbmRsZSB0aGUgYmFsbC4gIFRoZSBzdXJwcmlzZSBpcyB0aGF0IHRoZSBjZW50ZXJzIGhhZCBhIG11Y2ggaGlnaGVyIHBvaW50cy1wZXItc2hvdCByYXRpbyBidXQgYWxzbyBsZXNzIGJhbGwgdGltZSwgd2hpY2ggY29udHJpYnV0ZWQgdG8gdGhlIGhpZ2hlciBhdmVyYWdlIHNjb3JlIGFuZCBsb3dlciByYW5raW5nLg0KYGBge3J9DQphdmdfcG9zaXRpb25fcmFua3MgPC0gc3FsZGYoIlNFTEVDVCBQb3NpdGlvbiwgQ09VTlQoUG9zaXRpb24pIEFTIE51bWJlciwgQVZHKFJhbmspIEFTIFJhbmssIEFWRyhBZ2UpIEFzIEFnZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQVZHKFBvaW50c19QZXJfU2hvdCkgQVMgUG9pbnRzX1Blcl9TaG90LCBBVkcoQmFsbF9UaW1lKSBBUyBCYWxsX1RpbWUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFWRyhQb2ludF9Ub3RhbCkgQVMgUG9pbnRfVG90YWwsIEFWRyhUb3RhbF9TaG90cykgQXMgVG90YWxfU2hvdHMNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRlJPTSBwbGF5ZXJfc3RhdHNfcmFua2luZ19kYXRhDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIEdST1VQIEJZIFBvc2l0aW9uDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIE9SREVSIEJZIFJhbmssIE51bWJlciIpDQoNCiNQZXJmb3JtIENsZWFuaW5nDQphdmdfcG9zaXRpb25fcmFua3MgPC0gYXMuZGF0YS5mcmFtZShsYXBwbHkoYXZnX3Bvc2l0aW9uX3JhbmtzLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRlVOID0gZnVuY3Rpb24oWCwgbiA9IDIpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZighaXMubnVtZXJpYyhYKSkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4oYXMuY2hhcmFjdGVyKFgpKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbHNlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuKHJvdW5kKFgsIGRpZ2l0cyA9IG4pKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQ0KDQphdmdfcG9zaXRpb25fcmFua3NfcGxvdGx5IDwtIHBsb3RfbHkoYXZnX3Bvc2l0aW9uX3JhbmtzKSAlPiUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGFkZF9iYXJzKHggPSB+UG9zaXRpb24sIHkgPSB+UmFuaywgY29sb3IgPSB+UG9pbnRzX1Blcl9TaG90LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gfk51bWJlciwgY29sb3JzID0gIlJkQnUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXh0ID0gfnBhc3RlKCI8L2JyPk51bWJlcjogIiwgTnVtYmVyLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI8L2JyPlBvaW50cyBQZXIgU2hvdDogIiwgUG9pbnRzX1Blcl9TaG90LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI8L2JyPlBvaW50IFRvdGFsOiAiLCBQb2ludF9Ub3RhbCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiPC9icj5TaG90IFRvdGFsOiAiLCBUb3RhbF9TaG90cywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiPC9icj5CYWxsIFRpbWU6ICIsIEJhbGxfVGltZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiPC9icj5BZ2U6ICIsIEFnZSkgKSAlPiUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbGF5b3V0KHRpdGxlID0gIlBvc2l0aW9uYWwgUmFua2luZ3MiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gIlBvc2l0aW9uIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiQXZlcmFnZSBSYW5raW5nIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiYXJnYXAgPSB+TnVtYmVyKQ0KDQphdmdfcG9zaXRpb25fcmFua3NfZ2cgPC0gZ2dwbG90KCkgKw0KICAgICAgICAgICAgICAgICAgICAgICAgIGdlb21fY29sKGRhdGEgPSBhdmdfcG9zaXRpb25fcmFua3MsIGFlcyh4ID0gUG9zaXRpb24sIHkgPSBSYW5rLCBmaWxsID0gUG9pbnRzX1Blcl9TaG90KSkNCg0KI2dncGxvdDIgR3JhcGhpY3MNCiNhdmdfcG9zaXRpb25fcmFua3NfZ2cNCg0KI1Bsb3RseSBHcmFwaGljDQphdmdfcG9zaXRpb25fcmFua3NfcGxvdGx5DQpgYGANCg0KVGhpcyBnZ3Bsb3QgZ3JhcGhpYyBzaG93cyBob3cgdGhpcyByYW5raW5nIHN5c3RlbSBwbGF5cyBvdXQgd2l0aCByZXNwZWN0IHRvIGEgcGxheWVyJ3MgcG9pbnQgdG90YWwsIGFjY291bnRpbmcgZm9yIHBvc2l0aW9uIGFuZCBiYWxsIHRpbWUgYXMgd2VsbC4gIEFzIG9uZSB3b3VsZCBleHBlY3QgcGxheWVycyB3aXRoIGhpZ2ggcG9pbnQgdG90YWxzIGhhdmUgdGhlIGhpZ2hlc3QgcmFua2luZ3MsIGFuZCB0aGlzIGdlbmVyYWxseSBjb3JyZXNwb25kcyB0byBtb3JlIGJhbGwgdGltZS4gTG9va2luZyBhdCB0aGUgZ3JhcGhpYyBpdCBpcyBhbHNvIGNsZWFyIHRoYXQgZGlmZmVyZW50IHBvc2l0aW9ucyBoYXZlIGRlZmluZWQgY2x1c3RlcmluZyB3aXRoaW4gdGhlIGRhdGEsIHNvbWV0aGluZyB0aGF0IGNvdWxkIGJlIGV4cGxvcmVkIGZ1cnRoZXIuDQpgYGB7cn0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgDQpwbGF5ZXJfcG9zaXRpb25fcmFua3NfcGxvdGx5IDwtICBwbG90X2x5KCkgJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgIGFkZF9kYXRhKHBsYXllcl9zdGF0c19yYW5raW5nX2RhdGEpICU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICBhZGRfbWFya2Vycyh4ID0gflJhbmtpbmcsIHkgPSB+UG9pbnRfVG90YWwsIHogPSB+UG9pbnRzX1Blcl9TaG90LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gfkJhbGxfVGltZSwgY29sb3IgPSB+UG9zaXRpb24sIGNvbG9ycyA9ICJTcGVjdHJhbCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRleHQgPSB+cGFzdGUoIjwvYnI+UGxheWVyOiAiLCBQbGF5ZXIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjwvYnI+UmFuazogIiwgUmFuaywgIiwgVmFsdWU6ICIsIFJhbmtpbmcsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjwvYnI+UG9zaXRpb246ICIsIFBvc2l0aW9uLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI8L2JyPlBvaW50cyBQZXIgU2hvdDogIiwgUG9pbnRzX1Blcl9TaG90LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI8L2JyPlBvaW50IFRvdGFsOiAiLCBQb2ludF9Ub3RhbCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiPC9icj5TaG90IFRvdGFsOiAiLCBUb3RhbF9TaG90cywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiPC9icj5CYWxsIFRpbWU6ICIsIEJhbGxfVGltZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiPC9icj5BZ2U6ICIsIEFnZSkgKSAlPiUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbGF5b3V0KHRpdGxlID0gIlBsYXllciBQb3NpdGlvbmFsIFJhbmtpbmdzIikNCg0KICAgICAgICAgICAgICAgICAgICAgICAgICANCnBsYXllcl9wb3NpdGlvbl9yYW5rc19nZyA8LSBnZ3Bsb3QoKSArIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlb21fcG9pbnQoZGF0YSA9IHBsYXllcl9zdGF0c19yYW5raW5nX2RhdGEsIGFlcyh4ID0gUmFua2luZywgeSA9IFBvaW50X1RvdGFsLCBzaXplID0gQmFsbF9UaW1lLCBjb2xvciA9IFBvc2l0aW9uKSkNCg0KI2dncGxvdDIgR3JhcGhpYw0KI3BsYXllcl9wb3NpdGlvbl9yYW5rc19nZw0KDQojUGxvdGx5IEdyYXBoaWMNCnBsYXllcl9wb3NpdGlvbl9yYW5rc19wbG90bHkNCg0KYGBgDQoNCg0KVGhlIHBvc2l0aW9uYWwgcmFua2luZ3MgZXhpc3QgaW4gY2x1c3RlcnMsIHdoaWNoIG1lYW5zIHRoYXQgdGhlIHJhbmtpbmdzIGZvciBwbGF5ZXJzIG9mIHRoZSBzYW1lIHBvc2l0aW9uIGFyZSBncm91cGVkIGFyb3VuZCBhbiBleHBlY3RlZCB2YWx1ZSAoYXZlcmFnZSkgd2l0aCBzb21lIHN0YW5kYXJkIGRldmlhdGlvbiB3aXRoaW4gdGhlIGdyb3VwaW5nLiAgQnkgbW9kZWxpbmcgdGhlIHByb2JhYmlsaXRpZXMgb2YgcGxheWVycyBpbiBlYWNoIHBvc2l0aW9uIGhhdmluZyBhIGNlcnRhaW4gcmFua2luZyBhcyBhIG5vcm1hbCBkaXN0cmlidXRpb24sIGEgc2V0IG9mIGJvdW5kYXJpZXMgZm9yIGlkZW50aWZ5aW5nIGEgcGxheWVyJ3MgcG9zaXRpb24gYnkgdGhlaXIgcmFua2luZyB3aXRoaW4gdGhpcyBzeXN0ZW0gYmVnaW5zIHRvIGFwcGVhci4gRm9yIGV4YW1wbGUsIHRoZSBtb3N0IGNvbmZpZGVudCBlc3RpbWF0ZSB0aGF0IGEgcGxheWVyIHdpdGggYSBjZXJ0YWluIHJhbmtpbmcgcGxheXMgYSBjZXJ0YWluIHBvc2l0aW9uIHdpbGwgbWFwIGRpcmVjdGx5IHRvIHRoZSBwb3NpdGlvbmFsIGRpc3RyaWJ1dGlvbiB3aXRoIHRoZSBoaWdoZXN0IHZhbHVlIGZvciB0aGF0IHJhbmtpbmcgDQooZS5nLiBpZiBhIHBsYXllcidzIHJhbmtpbmcgaXMgbGVzcyB0aGFuIDEuMDAsIHRoZW4gJ2NlbnRlcicgd291bGQgYmUgdGhlIGJlc3QgZXN0aW1hdGUgZm9yIHRoZWlyIHBvc2l0aW9uLikNCmBgYHtyfQ0KDQpwb3NpdGlvbnMgPC0gdW5saXN0KHNxbGRmKCJTRUxFQ1QgRElTVElOQ1QoIHBvc2l0aW9uICkgRlJPTSBwbGF5ZXJfc3RhdHNfcmFua2luZ19kYXRhIikpDQpwb3NpdGlvbl9yYW5rc19zdGF0cyA8LSBhcy5kYXRhLmZyYW1lKG1hdHJpeCgwLCBuY29sID0gbGVuZ3RoKHBvc2l0aW9ucyksIG5yb3cgID0gMikpDQpjb2xuYW1lcyhwb3NpdGlvbl9yYW5rc19zdGF0cykgPC0gcG9zaXRpb25zDQpyb3duYW1lcyhwb3NpdGlvbl9yYW5rc19zdGF0cykgPC0gYygibWVhbiIsICJzdGREaXN0IikNCmNvbG9ycyA8LSBjKCJyZWQiLCAiZ3JlZW4iLCAiYmx1ZSIsICJ5ZWxsb3ciLCAicHVycGxlIikNCm4gPC0gMQ0KcG9zaXRpb25fcmFua3NfZGlzdF9wbG90bHkgPC0gcGxvdF9seSgpDQoNCmZvcihwb3MgaW4gcG9zaXRpb25zKQ0Kew0KICBkYXRhIDwtIHBsYXllcl9zdGF0c19yYW5raW5nX2RhdGFbcGxheWVyX3N0YXRzX3JhbmtpbmdfZGF0YSRQb3NpdGlvbiA9PSBwb3MsICJSYW5raW5nIl0NCiAgcG9zaXRpb25fcmFua3Nfc3RhdHNbLHBvc10gPSBjKG1lYW4oZGF0YSksIHNkKGRhdGEpKSAgDQogIHl2YWwgPC0gZG5vcm0oZGF0YSwgcG9zaXRpb25fcmFua3Nfc3RhdHNbMSxwb3NdLCBwb3NpdGlvbl9yYW5rc19zdGF0c1syLHBvc10pDQogIA0KICBwb3NpdGlvbl9yYW5rc19kaXN0X3Bsb3RseSA8LSBwb3NpdGlvbl9yYW5rc19kaXN0X3Bsb3RseSAlPiUgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFkZF9tYXJrZXJzKHggPSBkYXRhLCB5ID0geXZhbCwgY29sb3JzID0gIlNwZWN0cmFsIiwgbmFtZSA9IHBvcykgJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFkZF9saW5lcyh4ID0gZGF0YSwgeSA9IHl2YWwsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmFtZSA9IHBvcywgdGV4dCA9IHBhc3RlKCJNZWFuIFJhbmtpbmc6ICIsIHBvc2l0aW9uX3JhbmtzX3N0YXRzWzEscG9zXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiPC9icj4gU3RkIERldmlhdGlvbjogIiwgcG9zaXRpb25fcmFua3Nfc3RhdHNbMiwgcG9zXSksIGNvbG9ycyA9ICJTcGVjdHJhbCIpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KfQ0KDQpwb3NpdGlvbl9yYW5rc19kaXN0X3Bsb3RseSA8LSBwb3NpdGlvbl9yYW5rc19kaXN0X3Bsb3RseSAlPiUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxheW91dCh0aXRsZSA9ICJQb3NpdGlvbiBSYW5raW5nIERpc3RyaWJ1dGlvbnMiLCB4YXhpcyA9IGxpc3QodGl0bGUgPSAiUmFua2luZyBWYWx1ZSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJQcm9iYWJpbGl0eSAlIikpDQoNCg0KZGF0YWZ1bGwgPC0gZGF0YS5mcmFtZSgiUmFua2luZyIgPSBwbGF5ZXJfc3RhdHNfcmFua2luZ19kYXRhW3BsYXllcl9zdGF0c19yYW5raW5nX2RhdGEkUG9zaXRpb24gPT0gIkMiLCAiUmFua2luZyJdWzE6NDddLA0KICAgICAgICAgICAgICAgICAgICAiUEYiID0gcGxheWVyX3N0YXRzX3JhbmtpbmdfZGF0YVtwbGF5ZXJfc3RhdHNfcmFua2luZ19kYXRhJFBvc2l0aW9uID09ICJQRiIsICJSYW5raW5nIl1bMTo0N10sDQogICAgICAgICAgICAgICAgICAgICJQRyIgPSBwbGF5ZXJfc3RhdHNfcmFua2luZ19kYXRhW3BsYXllcl9zdGF0c19yYW5raW5nX2RhdGEkUG9zaXRpb24gPT0gIlBHIiwgIlJhbmtpbmciXVsxOjQ3XSwNCiAgICAgICAgICAgICAgICAgICAgIlNGIiA9IHBsYXllcl9zdGF0c19yYW5raW5nX2RhdGFbcGxheWVyX3N0YXRzX3JhbmtpbmdfZGF0YSRQb3NpdGlvbiA9PSAiU0YiLCAiUmFua2luZyJdWzE6NDddLA0KICAgICAgICAgICAgICAgICAgICAiU0ciID0gcGxheWVyX3N0YXRzX3JhbmtpbmdfZGF0YVtwbGF5ZXJfc3RhdHNfcmFua2luZ19kYXRhJFBvc2l0aW9uID09ICJTRyIsICJSYW5raW5nIl1bMTo0N10pDQoNCmRhdGFmdWxsJFBlcmNlbnRhZ2UgPC0gZG5vcm0oZGF0YWZ1bGwkUmFua2luZywgcG9zaXRpb25fcmFua3Nfc3RhdHNbMSwxXSwgcG9zaXRpb25fcmFua3Nfc3RhdHNbMiwxXSkNCmRhdGFmdWxsJHZhbFBGIDwtIGRub3JtKGRhdGFmdWxsJFBGLCBwb3NpdGlvbl9yYW5rc19zdGF0c1sxLDJdLCBwb3NpdGlvbl9yYW5rc19zdGF0c1syLDJdKQ0KZGF0YWZ1bGwkdmFsUEc8LSBkbm9ybShkYXRhZnVsbCRQRywgcG9zaXRpb25fcmFua3Nfc3RhdHNbMSwzXSwgcG9zaXRpb25fcmFua3Nfc3RhdHNbMiwzXSkNCmRhdGFmdWxsJHZhbFNGIDwtIGRub3JtKGRhdGFmdWxsJFNGLCBwb3NpdGlvbl9yYW5rc19zdGF0c1sxLDRdLCBwb3NpdGlvbl9yYW5rc19zdGF0c1syLDRdKQ0KZGF0YWZ1bGwkdmFsU0cgPC0gZG5vcm0oZGF0YWZ1bGwkU0csIHBvc2l0aW9uX3JhbmtzX3N0YXRzWzEsNV0sIHBvc2l0aW9uX3JhbmtzX3N0YXRzWzIsNV0pDQoNCg0KcG9zaXRpb25fcmFua3NfZGlzdF9nZyA8LSBnZ3Bsb3QoZGF0YWZ1bGwpICsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VvbV9saW5lKGFlcyh4ID0gUmFua2luZywgeSA9IFBlcmNlbnRhZ2UsIGNvbG9yID0gIkMiLCBuYW1lID0gIkMiKSkgKw0KICAgICAgICAgICAgICAgICAgICAgICAgICBnZW9tX3BhdGgoYWVzKHggPSBQRiwgeSA9IHZhbFBGLCBjb2xvciA9ICJQRiIsIG5hbWUgPSAiUEYiKSkgKw0KICAgICAgICAgICAgICAgICAgICAgICAgICBnZW9tX3BhdGgoYWVzKHggPSBQRywgeSA9IHZhbFBHLCBjb2xvciA9ICJQRyIsIG5hbWUgPSAiUEciKSkgKw0KICAgICAgICAgICAgICAgICAgICAgICAgICBnZW9tX3BhdGgoYWVzKHggPSBTRiwgeSA9IHZhbFNGLCBjb2xvciA9ICJTRiIsIG5hbWUgPSAiU0YiKSkgKw0KICAgICAgICAgICAgICAgICAgICAgICAgICBnZW9tX3BhdGgoYWVzKHggPSBTRywgeSA9IHZhbFNHLCBjb2xvciA9ICJTRyIsIG5hbWUgPSAiU0ciKSkNCiNnZ3Bsb3QgR3JhcGhpYw0KI3Bvc2l0aW9uX3JhbmtzX2Rpc3RfZ2cNCg0KI3Bsb3RseSBHcmFwaGljDQpwb3NpdGlvbl9yYW5rc19kaXN0X3Bsb3RseQ0KDQpgYGANCg0KYGBge3J9DQoNCmBgYA0KDQoNCg0K